/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package avl.sv.server.sources;

import avl.sv.server.PermissionsSet;
import static avl.sv.server.sources.SolutionSourceKVStore.getPermissionsSets;
import avl.sv.shared.Annotations.Annotation;
import avl.sv.shared.Annotations.AnnotationSet;
import avl.sv.shared.ImageID;
import avl.sv.shared.Permissions;
import avl.sv.shared.SlideReference;
import avl.sv.shared.SlideSet;
import avl.sv.shared.SlideSets;
import avl.sv.shared.XML.AnnotationXML_Parser;
import avl.sv.shared.XML.AnnotationXML_Writer;
import avl.sv.shared.XML.AnnotationsXML_Parser;
import avl.sv.shared.sources.SlideSource;
import avl.sv.shared.sources.StudySource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.parsers.ParserConfigurationException;
import oracle.kv.Depth;
import oracle.kv.Direction;
import oracle.kv.KVStore;
import oracle.kv.Key;
import oracle.kv.KeyValueVersion;
import oracle.kv.Value;
import oracle.kv.ValueVersion;
import org.xml.sax.SAXException;

/**
 *
 * @author benbryan
 */
public class StudySourceKVStore extends StudySource{
        
    private final KVStore kvstore;
    private StudySourceKVStore(KVStore kvstore, String studyName, int id){
        super(studyName, id);
        this.kvstore = kvstore;
    }
        
    private static int createID(KVStore kvstore, String name){
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        kvstore.putIfAbsent(Key.createKey(major), Value.EMPTY_VALUE);
        major.add(""); // this gets replaced below
        while (true){
            //TODO: this could cause things to hang, maybe. 
            int r = (int) (Math.random()*Math.pow(2, 32));
            major.set(1, String.valueOf(r));
            ValueVersion vv = kvstore.get(Key.createKey(major));
            if (vv == null){
                kvstore.put(Key.createKey(major), Value.EMPTY_VALUE);
                ArrayList<String> minor = new ArrayList<>();
                minor.add("name");
                kvstore.put(Key.createKey(major, minor),Value.createValue(name.getBytes()));
                return r;
            }
        }
    }
    
    public static StudySourceKVStore get(KVStore kvstore, int id){
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        kvstore.putIfAbsent(Key.createKey(major), Value.EMPTY_VALUE);
        major.add(String.valueOf(id));
        ValueVersion temp = kvstore.get(Key.createKey(major));
        if (temp == null){
            return null;
        } else {
            ArrayList<String> minor = new ArrayList<>();
            minor.add("name");
            temp = kvstore.get(Key.createKey(major, minor));
            String name;
            if (temp != null){
                name = new String(temp.getValue().getValue());
            } else {
                name = "failed to get solution name";
            }
            return new StudySourceKVStore(kvstore, name, id);
        }
    }
    
    public static StudySourceKVStore create(KVStore kvstore, String studyName, String userName){
        int id = createID(kvstore, studyName);
        StudySourceKVStore ss = new StudySourceKVStore(kvstore, studyName, id);
        ss.setPermission(userName, Permissions.ADMIN); 
        return ss;
    }
    
    
    public Permissions getPermissions(String userName){
        ArrayList<PermissionsSet> pSets = getPermissionsSets(kvstore, userName);
        for (PermissionsSet pSet:pSets){
            if (pSet.getID() == getId()){
                return pSet.getPermission();
            }
        }
        return Permissions.DENIED;
    }
        
    public static ArrayList<PermissionsSet> getPermissionsSets(KVStore kvstore, final String userName) {
        ArrayList<String> major = new ArrayList<>();
        major.add("user");
        major.add(userName);
        ArrayList<String> minor = new ArrayList<>();
        minor.add("permissions");
        minor.add("study");
        ArrayList<PermissionsSet> permissionSet = new ArrayList<>();
        ValueVersion vv = kvstore.get(Key.createKey(major, minor));
        if ((vv == null) || (vv.getValue() == null)){
            return permissionSet;
        }
        String entries[] = new String(vv.getValue().getValue()).split(System.lineSeparator());
        for (String entry:entries){
            // entry should be in the format   permission:ID
            String[] str = entry.split(",");
            Permissions p;
            if (str.length != 2){
                continue;
            }
            try{
                p = Permissions.valueOf(str[0]);
            } catch (Exception ex){
                continue;
            } 
            permissionSet.add(new PermissionsSet(p, Integer.valueOf(str[1])));
        }
        return permissionSet;
    }  
    
    public static long getPermissionsSetsVersion(KVStore kvstore, final String userName) {
        ArrayList<String> major = new ArrayList<>();
        major.add("user");
        major.add(userName);
        ArrayList<String> minor = new ArrayList<>();
        minor.add("permissions");
        minor.add("study");
        ValueVersion vv = kvstore.get(Key.createKey(major, minor));
        if ((vv == null) || (vv.getValue() == null)){
            Value v = Value.createValue("".getBytes());
            kvstore.put(Key.createKey(major, minor), v);
            vv = kvstore.get(Key.createKey(major, minor));
        } 
        return vv.getVersion().getVersion();
    }  
    
    private static void setPermissionsSets(final KVStore kvstore, final String userName, final ArrayList<PermissionsSet> permissionSets) {
        ArrayList<String> major = new ArrayList<>();
        major.add("user");
        major.add(userName);
        ArrayList<String> minor = new ArrayList<>();
        minor.add("permissions");
        minor.add("study");
        StringBuilder sb = new StringBuilder();
        for (PermissionsSet permissionSet:permissionSets){
            // entry should be in the format   name:permission:ID
            String entry = permissionSet.getPermission().name() + "," + permissionSet.getID() + System.lineSeparator();
            sb.append(entry);
        }
        kvstore.put(Key.createKey(major,minor), Value.createValue(sb.toString().getBytes()));
    }  
   
    public void setPermission( final String userName, final Permissions permission) {
        PermissionsSet newSet = new PermissionsSet(permission, id);
        ArrayList<PermissionsSet> permissionSets = getPermissionsSets(kvstore, userName);
        boolean entryUpdated = false;
        for (int i = 0; i < permissionSets.size(); i++){
            if (permissionSets.get(i).getID() == id){
                if (permission == Permissions.DENIED){
                    permissionSets.remove(i);
                } else {
                    permissionSets.set(i, newSet);
                }
                entryUpdated = true;
                break;
            }
        }
        if (entryUpdated == false){
            permissionSets.add(newSet);                
        }
        setPermissionsSets( kvstore, userName, permissionSets);
        updateUserList(userName, permission);
    }

    public String getUserList(){   
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        kvstore.putIfAbsent(Key.createKey(major), Value.EMPTY_VALUE);
        major.add(String.valueOf(getId()));
        ArrayList<String> minor = new ArrayList<>();
        minor.add("users");     
        ValueVersion vv = kvstore.get(Key.createKey(major, minor));
        if (vv == null){
            return "";
        } else {
            return new String(vv.getValue().getValue());
        }
    }
    private void updateUserList(String userName, Permissions permission){   
        HashSet<String> users = new HashSet<>();
        users.addAll(Arrays.asList(getUserList().split(";")));
        users.add(userName);
        if (permission.equals(Permissions.DENIED)){
            users.remove(userName);
        }
        StringBuilder sb = new StringBuilder();
        for (String user:users){
            sb.append(user).append(";");
        }
        setUserList(sb.toString());
    }
    private void setUserList(String userList){
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        kvstore.putIfAbsent(Key.createKey(major), Value.EMPTY_VALUE);
        major.add(String.valueOf(getId()));
        ArrayList<String> minor = new ArrayList<>();
        minor.add("users");     
        kvstore.put(Key.createKey(major, minor), Value.createValue(userList.getBytes()));        
    }
    
    public long getVersion(){
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        kvstore.putIfAbsent(Key.createKey(major), Value.EMPTY_VALUE);
        major.add(String.valueOf(id));
        ValueVersion vv = kvstore.get(Key.createKey(major));
        if (vv == null){
            kvstore.put(Key.createKey(major), Value.EMPTY_VALUE);
            vv = kvstore.get(Key.createKey(major));
        }
        return vv.getVersion().getVersion();
    }
        
    
    @Override
    public void updateSlideSets() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public SlideSets getSlideSets() {
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        major.add(String.valueOf(id));
        ArrayList<String> minor = new ArrayList<>();
        minor.add("annotations");
        
        SlideSets dataSets = new SlideSets();
        Iterator<Key> datasetIter = kvstore.multiGetKeysIterator(Direction.FORWARD, 0, Key.createKey(major, minor), null ,Depth.CHILDREN_ONLY);
        while (datasetIter.hasNext()) {
            Key datasetKey = datasetIter.next();
            Iterator<KeyValueVersion> slideIter = kvstore.multiGetIterator(Direction.FORWARD, 0, datasetKey, null, Depth.CHILDREN_ONLY);
            while (slideIter.hasNext()){
                KeyValueVersion vv = slideIter.next();
                java.util.List<String> path = vv.getKey().getMinorPath();
                String slideName = path.get(path.size()-1);
                String datasetName = path.get(path.size()-2);
                byte slideHash[] = vv.getValue().getValue();
                               
                SlideReference sn = new SlideReference(datasetName, slideName, slideHash);
                
                boolean added = false;
                for (SlideSet dataSet:dataSets.getSlideSets()){
                    if (datasetName.equals(dataSet.getSlideSetName())){
                        dataSet.add(sn);
                        added = true;
                        break;
                    }
                }
                if (added == false){
                    SlideSet dsn = new SlideSet(datasetName);
                    dsn.add(sn);
                    dataSets.add(dsn);
                }
            }
        }     
        return dataSets;    
    }
    
    @Override
    public void clearAnnotations(SlideReference slideReference){
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        major.add(String.valueOf(id));
        kvstore.put(Key.createKey(major), Value.EMPTY_VALUE); // to update verion
        ArrayList<String> minor = new ArrayList<>();
        minor.add("annotations");       
        minor.add(slideReference.slideSetName);
        minor.add(slideReference.slideName);
        Key key = Key.createKey(major, minor);
        
        ValueVersion vv = kvstore.get(Key.createKey(major, minor));
        if (vv == null){
            kvstore.put(Key.createKey(major, minor), Value.createValue(slideReference.hash));
        } else {
           byte[] hashInStore = vv.getValue().getValue();
             if (!ImageID.hashesAreEqual(slideReference.hash, hashInStore)){
                 return ;
             } 
        }
        
        SortedSet<Key> keys = kvstore.multiGetKeys(key, null, Depth.PARENT_AND_DESCENDANTS);
        for (Key k:keys){
            kvstore.delete(k);
        }

    }
    
    public String getAnnotationsXML(SlideReference slideReference) {
        byte[] hash = slideReference.hash;
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        major.add(String.valueOf(id));
        ArrayList<String> minor = new ArrayList<>();
        minor.add("annotations");       
        minor.add(slideReference.slideSetName);
        minor.add(slideReference.slideName);
        Key key = Key.createKey(major, minor);
        
        ValueVersion vv = kvstore.get(Key.createKey(major, minor));
        if (vv == null){
            kvstore.put(Key.createKey(major, minor), Value.createValue(hash));
        } else {
           byte[] hashInStore = vv.getValue().getValue();
             if (!ImageID.hashesAreEqual(hash, hashInStore)){
                 return null;
             } 
        }
        
        SortedMap<Key, ValueVersion> map = kvstore.multiGet(key, null, Depth.CHILDREN_ONLY);
        
        ArrayList<Annotation> annotations = new ArrayList<>();
        for (Iterator<Key> it = map.keySet().iterator(); it.hasNext();) {
            key = it.next();
            vv = map.get(key);
            String xml = new String(vv.getValue().getValue());
            AnnotationXML_Parser parser = new AnnotationXML_Parser();
            try {
                Annotation anno = parser.parse(xml);
                annotations.add(anno);
            } catch (    ParserConfigurationException | SAXException | IOException ex) {
                Logger.getLogger(StudySourceKVStore.class.getName()).log(Level.SEVERE, null, ex);
            }
            
        }
        String xml = AnnotationXML_Writer.getXMLString(annotations);
        return xml;
    }

    public String setAnnotationsXML(SlideReference slideReference, String xml) {
        byte[] hash = slideReference.hash;
        clearAnnotations(slideReference);
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        kvstore.putIfAbsent(Key.createKey(major), Value.EMPTY_VALUE);
        major.add(String.valueOf(id));
        kvstore.put(Key.createKey(major), Value.EMPTY_VALUE); // to update verion
        ArrayList<String> minor = new ArrayList<>();
        minor.add("annotations");       
        kvstore.putIfAbsent(Key.createKey(major, minor), Value.EMPTY_VALUE);
        minor.add(slideReference.slideSetName);
        kvstore.putIfAbsent(Key.createKey(major, minor), Value.EMPTY_VALUE);
        minor.add(slideReference.slideName);
        ValueVersion vv = kvstore.get(Key.createKey(major, minor));
        if (vv == null){
            kvstore.put(Key.createKey(major, minor), Value.createValue(hash));
        } else {
           byte[] hashInStore = vv.getValue().getValue();
             if (!ImageID.hashesAreEqual(hash, hashInStore)){
                 return "Failed";
             } 
        }      
        
        ArrayList<Annotation> annotations;
        AnnotationsXML_Parser parser = new AnnotationsXML_Parser();
        try {
            annotations = parser.parse(xml);
        } catch (ParserConfigurationException | SAXException | IOException ex) {
            Logger.getLogger(StudySourceKVStore.class.getName()).log(Level.SEVERE, null, ex);
            return "Failed";
        }
        
        for (Annotation anno:annotations){
            xml = AnnotationXML_Writer.getXMLString(anno);
            minor = new ArrayList<>();
            minor.add("annotations");   
            minor.add(slideReference.slideSetName);
            minor.add(slideReference.slideName);
            minor.add(anno.name);
            kvstore.put(Key.createKey(major, minor), Value.createValue(xml.getBytes()));
        }
        
        return "Success";
    }
    
    @Override
    public AnnotationSet getAnnotations(SlideReference slideReference){
        String annotationsXML = getAnnotationsXML(slideReference);
        if (annotationsXML == null){
            return null;
        }
        try {
            AnnotationsXML_Parser parser = new AnnotationsXML_Parser();
            ArrayList<Annotation> annos = parser.parse(annotationsXML);
            AnnotationSet as = new AnnotationSet(slideReference);
            for (Annotation anno:annos){
                as.add(anno);
            }           
            return as;
            
        } catch (IOException | ParserConfigurationException | SAXException ex) {
            Logger.getLogger(SlideSource.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;
        
    }
    
    @Override
    public String setAnnotations(AnnotationSet annotations) {
        SlideReference slideReference = annotations.getSlideReference();
        String xml = AnnotationXML_Writer.getXMLString(annotations.getAnnotations());
        return setAnnotationsXML(slideReference, xml);
    } 

    @Override
    public String setDescription(String description) {
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        major.add(String.valueOf(id));
        kvstore.put(Key.createKey(major), Value.EMPTY_VALUE); // to update verion
        ArrayList<String> minor = new ArrayList<>();
        minor.add("description");       
        Key key = Key.createKey(major, minor);
        kvstore.put(key, Value.createValue(description.getBytes()));
        return "Success";
    }

    @Override
    public String getDescription() {
        ArrayList<String> major = new ArrayList<>();
        major.add("study");
        major.add(String.valueOf(id));
        ArrayList<String> minor = new ArrayList<>();
        minor.add("description");       
        Key key = Key.createKey(major, minor);
        ValueVersion vv = kvstore.get(key);
        if ((vv == null) || (vv.getValue() == null) || (vv.getValue().getValue() == null)){
            return "No Description";
        } else {
            String desc = new String(vv.getValue().getValue());
            return desc;
        }
    }
    
    @Override
    public String delete(){
        if (getUserList().length() < 1){
            ArrayList<String> major = new ArrayList<>();
            major.add("study");
            kvstore.put(Key.createKey(major), Value.EMPTY_VALUE);
            major.add(String.valueOf(id));

            SortedSet<Key> keys = kvstore.multiGetKeys(Key.createKey(major), null, Depth.PARENT_AND_DESCENDANTS);
            for (Key k:keys){
                kvstore.delete(k);
            }
            return "Deleted";
        }
        return "Not Deleted";
    }

    @Override
    public boolean isStudyCurrent() {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
}
